---
title: DOM 操作與事件處理
description: 透過掌握 DOM 操作和事件處理來建立動態、互動式使用者介面，從後端邏輯轉向前端使用者互動。
---

# DOM 操作與事件處理

作為 Python 開發者，你習慣於處理資料結構和演算法。在前端開發中，文件物件模型（DOM）成為你建立互動式使用者介面的主要資料結構。本模組教你如何有效地操作 DOM 和處理使用者事件。

## 理解 DOM

### 從 Python 資料結構到 DOM

<PythonEditor title="資料結構操作對比" compare={true}>
```python !! py
# Python：處理資料結構（串列、字典）
import json
from datetime import datetime

class TaskManager:
    """
    Python 方式：處理原生資料結構
    - 直接操作串列和字典
    - 預設無視覺表示
    - 專注於資料邏輯和演算法
    """
    
    def __init__(self):
        self.tasks = []
        self.categories = {}
    
    def add_task(self, title, description, category="general"):
        """向資料結構新增新任務"""
        task = {
            "id": len(self.tasks) + 1,
            "title": title,
            "description": description,
            "category": category,
            "completed": False,
            "created_at": datetime.now().isoformat()
        }
        
        # 新增到主串列
        self.tasks.append(task)
        
        # 更新分類
        if category not in self.categories:
            self.categories[category] = []
        self.categories[category].append(task["id"])
        
        print(f"任務已新增：{task['title']}")
        return task
    
    def complete_task(self, task_id):
        """標記任務為已完成"""
        for task in self.tasks:
            if task["id"] == task_id:
                task["completed"] = True
                task["completed_at"] = datetime.now().isoformat()
                print(f"任務已完成：{task['title']}")
                return True
        return False
    
    def filter_tasks(self, completed=None, category=None):
        """根據條件過濾任務"""
        filtered = self.tasks
        
        if completed is not None:
            filtered = [t for t in filtered if t["completed"] == completed]
        
        if category:
            filtered = [t for t in filtered if t["category"] == category]
        
        return filtered
    
    def display_tasks(self):
        """在控制台顯示任務"""
        print("\n=== 任務清單 ===")
        for task in self.tasks:
            status = "✓" if task["completed"] else "○"
            print(f"{status} [{task['category']}] {task['title']}")
            print(f"   {task['description']}")
        print("===============")

# Python 用法：在記憶體中處理資料
task_manager = TaskManager()

# 新增一些任務
task_manager.add_task("學習 JavaScript", "掌握前端開發", "教育")
task_manager.add_task("建置網站", "建立個人作品集", "專案")
task_manager.add_task("學習演算法", "準備面試", "教育")

# 操作資料
task_manager.complete_task(1)

# 過濾和顯示
education_tasks = task_manager.filter_tasks(category="教育")
print(f"教育類任務：{len(education_tasks)} 個")

task_manager.display_tasks()
```

```javascript !! js
// JavaScript：處理 DOM（文件物件模型）
// DOM 是表示 HTML 文件的活動資料結構

class TaskDOMManager {
    /**
     * JavaScript 方式：處理 DOM 元素
     * - 內建視覺表示
     * - 元素具有資料和視覺屬性
     * - 更改立即反映在使用者介面中
     */
    
    constructor() {
        this.tasks = [];
        this.taskContainer = document.getElementById('tasks-container');
        this.taskForm = document.getElementById('task-form');
        this.setupEventListeners();
        this.initializeDOM();
    }
    
    initializeDOM() {
        // 如果不存在，建立初始 DOM 結構
        if (!this.taskContainer) {
            this.createTaskContainer();
        }
        if (!this.taskForm) {
            this.createTaskForm();
        }
    }
    
    createTaskContainer() {
        // 程式化建立 DOM 元素
        this.taskContainer = document.createElement('div');
        this.taskContainer.id = 'tasks-container';
        this.taskContainer.className = 'tasks-list';
        
        // 新增到文件
        document.body.appendChild(this.taskContainer);
    }
    
    createTaskForm() {
        // 建立新增任務的表單
        this.taskForm = document.createElement('form');
        this.taskForm.id = 'task-form';
        this.taskForm.innerHTML = `
            <div class="form-group">
                <input type="text" id="task-title" placeholder="任務標題" required>
                <input type="text" id="task-description" placeholder="描述">
                <select id="task-category">
                    <option value="general">一般</option>
                    <option value="education">教育</option>
                    <option value="project">專案</option>
                </select>
                <button type="submit">新增任務</button>
            </div>
        `;
        
        // 插入到 body 開頭
        document.body.insertBefore(this.taskForm, this.taskContainer);
    }
    
    addTask(title, description, category = "general") {
        /**
         * 新增任務：資料和 DOM 操作
         * - 儲存在資料結構中（如 Python）
         * - 建立視覺表示（前端獨有）
         */
        const task = {
            id: Date.now(), // 簡單的 ID 生成
            title: title,
            description: description,
            category: category,
            completed: false,
            createdAt: new Date().toISOString()
        };
        
        // 新增到資料結構
        this.tasks.push(task);
        
        // 為任務建立 DOM 元素
        const taskElement = this.createTaskElement(task);
        
        // 新增到 DOM 並帶有動畫
        this.taskContainer.appendChild(taskElement);
        
        // 新任務出現動畫
        taskElement.style.opacity = '0';
        taskElement.style.transform = 'translateY(-20px)';
        
        // 觸發動畫
        requestAnimationFrame(() => {
            taskElement.style.transition = 'all 0.3s ease';
            taskElement.style.opacity = '1';
            taskElement.style.transform = 'translateY(0)';
        });
        
        console.log(`任務已新增到 DOM：${task.title}`);
        return task;
    }
    
    createTaskElement(task) {
        // 建立任務的 DOM 表示
        const taskDiv = document.createElement('div');
        taskDiv.className = `task-item ${task.completed ? 'completed' : ''}`;
        taskDiv.dataset.taskId = task.id; // 在 DOM 中儲存 ID
        
        taskDiv.innerHTML = `
            <div class="task-header">
                <span class="task-category">${task.category}</span>
                <button class="complete-btn" data-action="complete">
                    ${task.completed ? '✓' : '○'}
                </button>
            </div>
            <h3 class="task-title">${task.title}</h3>
            <p class="task-description">${task.description}</p>
            <div class="task-actions">
                <button class="edit-btn" data-action="edit">編輯</button>
                <button class="delete-btn" data-action="delete">刪除</button>
            </div>
        `;
        
        return taskDiv;
    }
    
    completeTask(taskId) {
        // 更新資料和 DOM
        const task = this.tasks.find(t => t.id == taskId);
        if (!task) return false;
        
        // 更新資料
        task.completed = !task.completed;
        task.completedAt = task.completed ? new Date().toISOString() : null;
        
        // 更新 DOM 元素
        const taskElement = document.querySelector(`[data-task-id="${taskId}"]`);
        if (taskElement) {
            // 更新視覺狀態
            taskElement.classList.toggle('completed', task.completed);
            
            // 更新按鈕文字
            const completeBtn = taskElement.querySelector('.complete-btn');
            completeBtn.textContent = task.completed ? '✓' : '○';
            
            // 新增完成動畫
            if (task.completed) {
                taskElement.style.transform = 'scale(1.05)';
                setTimeout(() => {
                    taskElement.style.transform = 'scale(1)';
                }, 200);
            }
        }
        
        console.log(`任務${task.completed ? '已完成' : '已重新開啟'}：${task.title}`);
        return true;
    }
    
    deleteTask(taskId) {
        // 從資料和 DOM 中移除
        const taskIndex = this.tasks.findIndex(t => t.id == taskId);
        if (taskIndex === -1) return false;
        
        // 從資料中移除
        const task = this.tasks.splice(taskIndex, 1)[0];
        
        // 從 DOM 中移除並帶有動畫
        const taskElement = document.querySelector(`[data-task-id="${taskId}"]`);
        if (taskElement) {
            // 移除動畫
            taskElement.style.transition = 'all 0.3s ease';
            taskElement.style.opacity = '0';
            taskElement.style.transform = 'translateX(-100%)';
            
            // 動畫後移除
            setTimeout(() => {
                taskElement.remove();
            }, 300);
        }
        
        console.log(`任務已刪除：${task.title}`);
        return true;
    }
    
    filterTasks(completed = null, category = null) {
        // 過濾任務並更新 DOM 顯示
        const allTaskElements = document.querySelectorAll('.task-item');
        
        allTaskElements.forEach(element => {
            const taskId = element.dataset.taskId;
            const task = this.tasks.find(t => t.id == taskId);
            
            let shouldShow = true;
            
            // 套用過濾器
            if (completed !== null && task.completed !== completed) {
                shouldShow = false;
            }
            
            if (category && task.category !== category) {
                shouldShow = false;
            }
            
            // 帶動畫顯示/隱藏
            if (shouldShow) {
                element.style.display = 'block';
                element.style.opacity = '1';
            } else {
                element.style.opacity = '0.3';
                element.style.display = 'none';
            }
        });
    }
    
    setupEventListeners() {
        // 設定表單提交
        document.addEventListener('submit', (event) => {
            if (event.target.id === 'task-form') {
                event.preventDefault();
                this.handleFormSubmit(event);
            }
        });
        
        // 使用事件委派設定任務互動
        document.addEventListener('click', (event) => {
            const action = event.target.dataset.action;
            const taskElement = event.target.closest('.task-item');
            
            if (!taskElement) return;
            
            const taskId = taskElement.dataset.taskId;
            
            switch (action) {
                case 'complete':
                    this.completeTask(taskId);
                    break;
                case 'edit':
                    this.editTask(taskId);
                    break;
                case 'delete':
                    this.deleteTask(taskId);
                    break;
            }
        });
    }
    
    handleFormSubmit(event) {
        const title = document.getElementById('task-title').value;
        const description = document.getElementById('task-description').value;
        const category = document.getElementById('task-category').value;
        
        if (title.trim()) {
            this.addTask(title, description, category);
            event.target.reset(); // 清空表單
        }
    }
    
    editTask(taskId) {
        // 內嵌編輯功能
        const taskElement = document.querySelector(`[data-task-id="${taskId}"]`);
        const task = this.tasks.find(t => t.id == taskId);
        
        if (!taskElement || !task) return;
        
        const titleElement = taskElement.querySelector('.task-title');
        const descriptionElement = taskElement.querySelector('.task-description');
        
        // 使元素可編輯
        titleElement.contentEditable = true;
        descriptionElement.contentEditable = true;
        titleElement.focus();
        
        // 新增編輯樣式
        titleElement.classList.add('editing');
        descriptionElement.classList.add('editing');
        
        // 處理 Enter 鍵或失焦時儲存
        const saveEdit = () => {
            task.title = titleElement.textContent.trim();
            task.description = descriptionElement.textContent.trim();
            
            titleElement.contentEditable = false;
            descriptionElement.contentEditable = false;
            titleElement.classList.remove('editing');
            descriptionElement.classList.remove('editing');
            
            console.log(`任務已編輯：${task.title}`);
        };
        
        titleElement.addEventListener('blur', saveEdit, { once: true });
        titleElement.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault();
                saveEdit();
            }
        }, { once: true });
    }
}

// 初始化基於 DOM 的任務管理器
const domTaskManager = new TaskDOMManager();

// 新增一些範例任務
domTaskManager.addTask("學習 DOM 操作", "掌握 JavaScript DOM API", "教育");
domTaskManager.addTask("建置互動式介面", "建立動態使用者介面", "專案");
domTaskManager.addTask("處理使用者事件", "實作事件監聽器", "教育");

// JavaScript DOM 特徵：
// 1. 視覺和資料表示結合
// 2. 使用者介面即時更新
// 3. 事件驅動的使用者互動
// 4. 動畫和視覺回饋
console.log("JavaScript：DOM 操作帶有視覺回饋和即時更新");
```
</PythonEditor>

## 事件驅動的使用者互動

### 從函數呼叫到事件處理器

<PythonEditor title="使用者互動模式" compare={true}>
```python !! py
# Python：基於函數的互動（CLI/程式化）
import cmd
import sys
from datetime import datetime

class TaskCLI(cmd.Cmd):
    """
    Python 方式：指令行介面
    - 順序指令處理
    - 基於文字的使用者互動
    - 無並行使用者操作
    """
    
    intro = '歡迎使用任務管理器 CLI。輸入 help 或 ? 檢視指令清單。\n'
    prompt = '(任務) '
    
    def __init__(self):
        super().__init__()
        self.tasks = []
        self.current_id = 1
    
    def do_add(self, line):
        """新增新任務：add <標題> | <描述>"""
        try:
            parts = line.split(' | ')
            title = parts[0].strip()
            description = parts[1].strip() if len(parts) > 1 else ""
            
            task = {
                'id': self.current_id,
                'title': title,
                'description': description,
                'completed': False,
                'created': datetime.now()
            }
            
            self.tasks.append(task)
            self.current_id += 1
            print(f"任務已新增：{title}")
            
        except Exception as e:
            print(f"錯誤：{e}")
    
    def do_list(self, line):
        """列出所有任務"""
        if not self.tasks:
            print("未找到任務。")
            return
        
        print("\n=== 任務清單 ===")
        for task in self.tasks:
            status = "✓" if task['completed'] else "○"
            print(f"{task['id']}. {status} {task['title']}")
            if task['description']:
                print(f"   {task['description']}")
        print("===============\n")
    
    def do_complete(self, line):
        """標記任務為已完成：complete <id>"""
        try:
            task_id = int(line.strip())
            task = next((t for t in self.tasks if t['id'] == task_id), None)
            
            if task:
                task['completed'] = True
                print(f"任務已完成：{task['title']}")
            else:
                print(f"未找到任務 {task_id}。")
                
        except ValueError:
            print("請提供有效的任務 ID。")
    
    def do_delete(self, line):
        """刪除任務：delete <id>"""
        try:
            task_id = int(line.strip())
            task = next((t for t in self.tasks if t['id'] == task_id), None)
            
            if task:
                self.tasks.remove(task)
                print(f"任務已刪除：{task['title']}")
            else:
                print(f"未找到任務 {task_id}。")
                
        except ValueError:
            print("請提供有效的任務 ID。")
    
    def do_quit(self, line):
        """退出程式"""
        print("再見！")
        return True
    
    def do_EOF(self, line):
        """處理 Ctrl+D"""
        return self.do_quit(line)

# Python CLI 用法
if __name__ == "__main__":
    print("Python CLI 任務管理器：")
    print("- 順序指令處理")
    print("- 基於文字的互動")
    print("- 單執行緒執行")
    
    # 這將啟動 CLI 迴圈
    # TaskCLI().cmdloop()
    
    # 模擬互動
    cli = TaskCLI()
    print("\n模擬 CLI 指令：")
    cli.do_add("學習 Python | 掌握後端開發")
    cli.do_add("建置 API | 建立 REST API")
    cli.do_list("")
    cli.do_complete("1")
    cli.do_list("")
```

```javascript !! js
// JavaScript：事件驅動使用者介面
// 多個並行使用者互動

class InteractiveTaskManager {
    /**
     * JavaScript 方式：事件驅動介面
     * - 多個同時的使用者互動
     * - 所有操作的視覺回饋
     * - 非同步事件處理
     */
    
    constructor() {
        this.tasks = [];
        this.currentId = 1;
        this.isEditing = false;
        this.setupInterface();
        this.setupEventListeners();
        this.setupKeyboardShortcuts();
    }
    
    setupInterface() {
        // 建立豐富的互動式介面
        document.body.innerHTML = `
            <div class="task-app">
                <header class="app-header">
                    <h1>互動式任務管理器</h1>
                    <div class="quick-stats">
                        <span id="total-tasks">0 個任務</span>
                        <span id="completed-tasks">0 個已完成</span>
                    </div>
                </header>
                
                <div class="add-task-section">
                    <form id="task-form" class="task-form">
                        <input type="text" id="task-title" placeholder="需要做什麼？" autocomplete="off">
                        <input type="text" id="task-description" placeholder="新增描述（可選）" autocomplete="off">
                        <button type="submit" class="add-btn">新增任務</button>
                    </form>
                </div>
                
                <div class="filter-section">
                    <button class="filter-btn active" data-filter="all">全部</button>
                    <button class="filter-btn" data-filter="pending">待辦</button>
                    <button class="filter-btn" data-filter="completed">已完成</button>
                </div>
                
                <div id="tasks-container" class="tasks-container">
                    <div class="empty-state">
                        <p>還沒有任務。在上面新增一個吧！</p>
                    </div>
                </div>
                
                <div class="bulk-actions">
                    <button id="select-all-btn">全選</button>
                    <button id="complete-selected-btn">完成選中</button>
                    <button id="delete-selected-btn">刪除選中</button>
                </div>
            </div>
        `;
        
        // 新增互動性的 CSS 樣式
        this.addStyles();
    }
    
    setupEventListeners() {
        // 表單提交 - 單個任務新增
        document.getElementById('task-form').addEventListener('submit', (e) => {
            e.preventDefault();
            this.handleTaskSubmission();
        });
        
        // 即時輸入驗證
        document.getElementById('task-title').addEventListener('input', (e) => {
            this.validateInput(e.target);
        });
        
        // 過濾按鈕
        document.querySelectorAll('.filter-btn').forEach(btn => {
            btn.addEventListener('click', (e) => {
                this.handleFilterChange(e.target.dataset.filter);
            });
        });
        
        // 批次操作
        document.getElementById('select-all-btn').addEventListener('click', () => {
            this.selectAllTasks();
        });
        
        document.getElementById('complete-selected-btn').addEventListener('click', () => {
            this.completeSelectedTasks();
        });
        
        document.getElementById('delete-selected-btn').addEventListener('click', () => {
            this.deleteSelectedTasks();
        });
        
        // 動態任務元素的事件委派
        document.getElementById('tasks-container').addEventListener('click', (e) => {
            this.handleTaskInteraction(e);
        });
        
        // 雙擊編輯（按鈕的替代方式）
        document.getElementById('tasks-container').addEventListener('dblclick', (e) => {
            if (e.target.classList.contains('task-title')) {
                this.enableInlineEditing(e.target);
            }
        });
        
        // 拖放重新排序
        document.getElementById('tasks-container').addEventListener('dragstart', (e) => {
            this.handleDragStart(e);
        });
        
        document.getElementById('tasks-container').addEventListener('dragover', (e) => {
            this.handleDragOver(e);
        });
        
        document.getElementById('tasks-container').addEventListener('drop', (e) => {
            this.handleDrop(e);
        });
    }
    
    setupKeyboardShortcuts() {
        // 全域鍵盤快速鍵
        document.addEventListener('keydown', (e) => {
            // Ctrl/Cmd + Enter：快速新增任務
            if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
                document.getElementById('task-title').focus();
            }
            
            // Escape：取消目前操作
            if (e.key === 'Escape') {
                this.cancelCurrentOperation();
            }
            
            // Delete 鍵：刪除選中的任務
            if (e.key === 'Delete' && !this.isEditing) {
                this.deleteSelectedTasks();
            }
            
            // Ctrl/Cmd + A：全選任務
            if ((e.ctrlKey || e.metaKey) && e.key === 'a' && !this.isEditing) {
                e.preventDefault();
                this.selectAllTasks();
            }
        });
    }
    
    handleTaskSubmission() {
        const titleInput = document.getElementById('task-title');
        const descriptionInput = document.getElementById('task-description');
        
        const title = titleInput.value.trim();
        const description = descriptionInput.value.trim();
        
        if (title) {
            this.addTask(title, description);
            titleInput.value = '';
            descriptionInput.value = '';
            titleInput.focus();
        }
    }
    
    addTask(title, description = '') {
        const task = {
            id: this.currentId++,
            title: title,
            description: description,
            completed: false,
            selected: false,
            createdAt: new Date(),
            order: this.tasks.length
        };
        
        this.tasks.push(task);
        this.renderTask(task);
        this.updateStats();
        this.updateEmptyState();
        
        // 顯示成功回饋
        this.showNotification(`任務"${title}"新增成功！`, 'success');
        
        console.log(`任務已新增：${title}`);
    }
    
    renderTask(task) {
        const tasksContainer = document.getElementById('tasks-container');
        
        const taskElement = document.createElement('div');
        taskElement.className = `task-item ${task.completed ? 'completed' : ''}`;
        taskElement.dataset.taskId = task.id;
        taskElement.draggable = true;
        
        taskElement.innerHTML = `
            <div class="task-content">
                <input type="checkbox" class="task-checkbox" ${task.selected ? 'checked' : ''}>
                <div class="task-info">
                    <h3 class="task-title" contenteditable="false">${task.title}</h3>
                    ${task.description ? `<p class="task-description">${task.description}</p>` : ''}
                </div>
                <div class="task-actions">
                    <button class="complete-btn" data-action="toggle-complete" title="切換完成狀態">
                        ${task.completed ? '✓' : '○'}
                    </button>
                    <button class="edit-btn" data-action="edit" title="編輯任務">✎</button>
                    <button class="delete-btn" data-action="delete" title="刪除任務">🗑</button>
                </div>
            </div>
        `;
        
        // 新增動畫
        taskElement.style.opacity = '0';
        taskElement.style.transform = 'translateY(-20px)';
        tasksContainer.appendChild(taskElement);
        
        // 觸發動畫
        requestAnimationFrame(() => {
            taskElement.style.transition = 'all 0.3s ease';
            taskElement.style.opacity = '1';
            taskElement.style.transform = 'translateY(0)';
        });
    }
    
    handleTaskInteraction(event) {
        const taskElement = event.target.closest('.task-item');
        if (!taskElement) return;
        
        const taskId = parseInt(taskElement.dataset.taskId);
        const action = event.target.dataset.action;
        
        switch (action) {
            case 'toggle-complete':
                this.toggleTaskCompletion(taskId);
                break;
            case 'edit':
                this.enableInlineEditing(taskElement.querySelector('.task-title'));
                break;
            case 'delete':
                this.deleteTask(taskId);
                break;
        }
        
        // 處理核取方塊選擇
        if (event.target.type === 'checkbox') {
            this.toggleTaskSelection(taskId);
        }
    }
    
    toggleTaskCompletion(taskId) {
        const task = this.tasks.find(t => t.id === taskId);
        if (!task) return;
        
        task.completed = !task.completed;
        
        const taskElement = document.querySelector(`[data-task-id="${taskId}"]`);
        taskElement.classList.toggle('completed', task.completed);
        
        const completeBtn = taskElement.querySelector('.complete-btn');
        completeBtn.textContent = task.completed ? '✓' : '○';
        
        // 動畫回饋
        taskElement.style.transform = 'scale(1.05)';
        setTimeout(() => {
            taskElement.style.transform = 'scale(1)';
        }, 150);
        
        this.updateStats();
        this.showNotification(
            `任務"${task.title}"${task.completed ? '已完成' : '已重新開啟'}！`,
            task.completed ? 'success' : 'info'
        );
    }
    
    enableInlineEditing(titleElement) {
        if (this.isEditing) return;
        
        this.isEditing = true;
        const originalTitle = titleElement.textContent;
        
        titleElement.contentEditable = true;
        titleElement.classList.add('editing');
        titleElement.focus();
        
        // 選擇所有文字
        const range = document.createRange();
        range.selectNodeContents(titleElement);
        const selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
        
        const saveEdit = () => {
            const newTitle = titleElement.textContent.trim();
            if (newTitle && newTitle !== originalTitle) {
                const taskId = parseInt(titleElement.closest('.task-item').dataset.taskId);
                const task = this.tasks.find(t => t.id === taskId);
                if (task) {
                    task.title = newTitle;
                    this.showNotification('任務更新成功！', 'success');
                }
            } else if (!newTitle) {
                titleElement.textContent = originalTitle;
            }
            
            titleElement.contentEditable = false;
            titleElement.classList.remove('editing');
            this.isEditing = false;
        };
        
        const cancelEdit = () => {
            titleElement.textContent = originalTitle;
            titleElement.contentEditable = false;
            titleElement.classList.remove('editing');
            this.isEditing = false;
        };
        
        // Enter 鍵儲存，Escape 鍵取消
        titleElement.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault();
                saveEdit();
            } else if (e.key === 'Escape') {
                e.preventDefault();
                cancelEdit();
            }
        });
        
        // 失焦時儲存
        titleElement.addEventListener('blur', saveEdit, { once: true });
    }
    
    handleFilterChange(filter) {
        // 更新活動過濾按鈕
        document.querySelectorAll('.filter-btn').forEach(btn => {
            btn.classList.remove('active');
        });
        document.querySelector(`[data-filter="${filter}"]`).classList.add('active');
        
        // 過濾任務
        document.querySelectorAll('.task-item').forEach(taskElement => {
            const taskId = parseInt(taskElement.dataset.taskId);
            const task = this.tasks.find(t => t.id === taskId);
            
            let shouldShow = true;
            
            switch (filter) {
                case 'pending':
                    shouldShow = !task.completed;
                    break;
                case 'completed':
                    shouldShow = task.completed;
                    break;
                case 'all':
                default:
                    shouldShow = true;
                    break;
            }
            
            taskElement.style.display = shouldShow ? 'block' : 'none';
        });
    }
    
    updateStats() {
        const total = this.tasks.length;
        const completed = this.tasks.filter(t => t.completed).length;
        
        document.getElementById('total-tasks').textContent = `${total} 個任務`;
        document.getElementById('completed-tasks').textContent = `${completed} 個已完成`;
    }
    
    updateEmptyState() {
        const emptyState = document.querySelector('.empty-state');
        const hasTasks = this.tasks.length > 0;
        emptyState.style.display = hasTasks ? 'none' : 'block';
    }
    
    showNotification(message, type = 'info') {
        // 建立並顯示通知
        const notification = document.createElement('div');
        notification.className = `notification ${type}`;
        notification.textContent = message;
        
        document.body.appendChild(notification);
        
        // 3秒後自動移除
        setTimeout(() => {
            notification.remove();
        }, 3000);
    }
    
    addStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .task-app { max-width: 800px; margin: 0 auto; padding: 20px; font-family: Arial, sans-serif; }
            .app-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
            .quick-stats span { margin-left: 20px; color: #666; }
            .task-form { display: flex; gap: 10px; margin-bottom: 20px; }
            .task-form input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
            .add-btn { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
            .filter-section { display: flex; gap: 10px; margin-bottom: 20px; }
            .filter-btn { padding: 8px 16px; border: 1px solid #ddd; background: white; cursor: pointer; border-radius: 4px; }
            .filter-btn.active { background: #007bff; color: white; }
            .task-item { background: white; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 10px; padding: 15px; transition: all 0.3s ease; }
            .task-item.completed { opacity: 0.7; }
            .task-content { display: flex; align-items: center; gap: 15px; }
            .task-info { flex: 1; }
            .task-title { margin: 0; cursor: text; }
            .task-title.editing { background: #f9f9f9; padding: 5px; border-radius: 3px; }
            .task-actions { display: flex; gap: 5px; }
            .task-actions button { background: none; border: none; cursor: pointer; padding: 5px; border-radius: 3px; }
            .task-actions button:hover { background: #f0f0f0; }
            .empty-state { text-align: center; color: #666; padding: 40px; }
            .bulk-actions { margin-top: 20px; display: flex; gap: 10px; }
            .bulk-actions button { padding: 8px 16px; border: 1px solid #ddd; background: white; cursor: pointer; border-radius: 4px; }
            .notification { position: fixed; top: 20px; right: 20px; padding: 15px; border-radius: 4px; color: white; z-index: 1000; }
            .notification.success { background: #28a745; }
            .notification.info { background: #17a2b8; }
            .notification.error { background: #dc3545; }
        `;
        document.head.appendChild(style);
    }
}

// 初始化互動式任務管理器
const interactiveTaskManager = new InteractiveTaskManager();

// 新增一些範例任務
interactiveTaskManager.addTask("學習 DOM 事件", "掌握 JavaScript 事件處理");
interactiveTaskManager.addTask("建置互動式介面", "建立響應式使用者介面");
interactiveTaskManager.addTask("處理使用者輸入", "處理表單和使用者互動");

// JavaScript 事件驅動特徵：
// 1. 多個並行使用者互動
// 2. 即時視覺回饋
// 3. 非同步事件處理
// 4. 豐富的使用者介面元素
console.log("JavaScript：事件驅動介面，具有即時互動和視覺回饋");
```
</PythonEditor>

## 總結和關鍵要點

DOM 操作和事件處理是建立互動式 Web 應用程式的基本技能：

### 🔄 **核心概念**
- **DOM 作為資料結構**：DOM 是一個活動的、可視化的資料結構，你可以像操作 Python 串列和字典一樣操作它
- **事件驅動程式設計**：使用者互動觸發非同步執行程式碼的事件
- **視覺回饋**：每個資料更改都可以立即反映在使用者介面中

### 🛠 **基本技術**
- **元素建立和操作**：程式化建立和修改 HTML 元素
- **事件監聽器**：回應使用者操作，如點擊、鍵盤輸入和表單提交
- **事件委派**：使用事件冒泡有效處理動態內容的事件

### 📱 **使用者體驗增強**
- **即時更新**：為使用者操作提供即時回饋
- **互動式元素**：建立具有動畫和過渡效果的引人入勝的介面
- **無障礙性**：確保你的介面適用於所有使用者，提供適當的鍵盤支援

在下一個模組中，我們將探索 Web 框架，以及它們如何簡化建置複雜互動式應用程式的過程，同時保持你在這裡學到的核心原則。
